import 'dart:async';

import 'package:built_collection/built_collection.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:logger/logger.dart';
import 'package:mobile_app.core/core/domain/config/config.dart';
import 'package:mobile_app.core/core/domain/entities/access_credentials.dart';
import 'package:mobile_app.core/core/domain/entities/google_apple_store_api/purchased_item/purchased_item.dart';
import 'package:mobile_app.core/core/domain/entities/premium_access_info.dart';
import 'package:mobile_app.core/core/domain/entities/premium_access_status.dart';
import 'package:mobile_app.core/core/domain/entities/purchased_item/purchased_item.dart';
import 'package:mobile_app.core/core/domain/entities/user.dart';
import 'package:mobile_app.core/core/domain/helpers/create_premium_access_status/helper.dart';
import 'package:mobile_app.core/core/domain/helpers/get_premium_access_info_from_purchased_items/helper.dart';
import 'package:mobile_app.core/core/domain/helpers/handle_access_credential_update/helper.dart';
import 'package:mobile_app.core/core/domain/repositories/purchased_item_api_repository/repository.dart';
import 'package:mobile_app.core/core/domain/repositories/user_repository/repository.dart';
import 'package:mobile_app.core/core/domain/repositories/user_secure_repository/repository.dart';
import 'package:mobile_app.core/core/domain/services/analytics_service/service.dart';
import 'package:mobile_app.core/core/domain/services/google_apple_store_api_service/service.dart';
import 'package:mobile_app.core/core/domain/utils/either_extension.dart';
import 'package:mobile_app.core/crashlytics/crashlytics.dart';

part 'failures.dart';

part 'result.dart';

abstract class HandleStoreApiPurchasedItemUpdate {
  Future<
      Either<StoreApiPurchasedItemUpdateHandlingFailure,
          StoreApiPurchasedItemUpdateHandlingResult?>> call({
    required GoogleAppleStoreApiPurchasedItem storeApiPurchasedItem,
    required String deviceId,
    required DateTime? demoExpirationTime,
    required bool introFinished,
  });
}

class HandleStoreApiPurchasedItemUpdateImpl implements HandleStoreApiPurchasedItemUpdate {
  const HandleStoreApiPurchasedItemUpdateImpl({
    required CreatePremiumAccessStatus createPremiumAccessStatus,
    required HandleAccessCredentialUpdate handleAccessCredentialUpdate,
    required GetPremiumAccessInfoFromPurchasedItems getPremiumAccessInfoFromPurchasedItems,
    required GoogleAppleStoreApiService googleAppleStoreApiService,
    required PurchasedItemApiRepository purchasedItemApiRepository,
    required UserRepository userRepository,
    required UserSecureRepository userSecureRepository,
    required AnalyticsService analyticsService,
  })  : _createPremiumAccessStatus = createPremiumAccessStatus,
        _handleAccessCredentialUpdate = handleAccessCredentialUpdate,
        _getPremiumAccessInfoFromPurchasedItems = getPremiumAccessInfoFromPurchasedItems,
        _googleAppleStoreApiService = googleAppleStoreApiService,
        _purchasedItemApiRepository = purchasedItemApiRepository,
        _userRepository = userRepository,
        _userSecureRepository = userSecureRepository,
        _analyticsService = analyticsService;

  final CreatePremiumAccessStatus _createPremiumAccessStatus;
  final HandleAccessCredentialUpdate _handleAccessCredentialUpdate;
  final GetPremiumAccessInfoFromPurchasedItems _getPremiumAccessInfoFromPurchasedItems;

  final GoogleAppleStoreApiService _googleAppleStoreApiService;
  final PurchasedItemApiRepository _purchasedItemApiRepository;
  final UserRepository _userRepository;
  final UserSecureRepository _userSecureRepository;

  final AnalyticsService _analyticsService;

  @override
  Future<
      Either<StoreApiPurchasedItemUpdateHandlingFailure,
          StoreApiPurchasedItemUpdateHandlingResult?>> call({
    required GoogleAppleStoreApiPurchasedItem storeApiPurchasedItem,
    required String deviceId,
    required DateTime? demoExpirationTime,
    required bool introFinished,
  }) async {
    switch (storeApiPurchasedItem) {
      case AppStoreApiPurchasedItem():
        return _handleAppStoreApiPurchasedItemUpdate(
          appStoreApiPurchasedItem: storeApiPurchasedItem,
          deviceId: deviceId,
          demoExpirationTime: demoExpirationTime,
          introFinished: introFinished,
        );

      case GooglePlayApiPurchasedItem():
        return _handleGooglePlayApiPurchasedItemUpdate(
          googlePlayApiPurchasedItem: storeApiPurchasedItem,
          deviceId: deviceId,
          demoExpirationTime: demoExpirationTime,
          introFinished: introFinished,
        );
    }
  }

  Future<
      Either<StoreApiPurchasedItemUpdateHandlingFailure,
          StoreApiPurchasedItemUpdateHandlingResult?>> _handleAppStoreApiPurchasedItemUpdate({
    required AppStoreApiPurchasedItem appStoreApiPurchasedItem,
    required String deviceId,
    required DateTime? demoExpirationTime,
    required bool introFinished,
  }) async {
    final transactionId = appStoreApiPurchasedItem.transactionId;
    final transactionState = appStoreApiPurchasedItem.transactionState;

    if (transactionState == AppStoreApiTransactionState.failed ||
        transactionState == AppStoreApiTransactionState.restored) {
      await _googleAppleStoreApiService.finishTransactionIos(
        transactionId: transactionId,
      );

      return right(null);
    }

    if (transactionState != AppStoreApiTransactionState.purchased) {
      Logger().e('Unexpected transaction state: $transactionState', null, StackTrace.current);

      Crashlytics().recordError(
        error: Exception('Unexpected transaction state: $transactionState'),
        stackTrace: StackTrace.current,
        fatal: false,
      );

      const failure = ServerStoreApiPurchasedItemUpdateHandlingFailure();

      return left(failure);
    }

    final startTime = DateTime.now();

    var apiPurchasedItemEffective = false;
    late PurchasedItemRestorationResult purchasedItemRestorationResult;

    while (DateTime.now().difference(startTime) < const Duration(seconds: 20)) {
      final purchasedItemRestorationFailureOrResult =
          await _purchasedItemApiRepository.restorePurchasedItems(
        deviceId: deviceId,
        appStoreReceipt: appStoreApiPurchasedItem.transactionReceipt,
        googlePlayPurchasedItemIdentifiers: null,
      );

      if (purchasedItemRestorationFailureOrResult.isLeft()) {
        final purchasedItemRestorationFailure = purchasedItemRestorationFailureOrResult.asLeft();

        switch (purchasedItemRestorationFailure) {
          case ServerPurchasedItemRestorationFailure():
            const failure = ServerStoreApiPurchasedItemUpdateHandlingFailure();
            return left(failure);

          case NetworkPurchasedItemRestorationFailure():
            const failure = NetworkStoreApiPurchasedItemUpdateHandlingFailure();
            return left(failure);

          case UnauthorizedPurchasedItemRestorationFailure():
            const updatedUser = null;
            const updatedAccessCredentials = null;

            _handleAccessCredentialUpdate(
              accessCredentials: updatedAccessCredentials,
            );

            unawaited(_userRepository.saveUser(
              user: updatedUser,
            ));

            if (Config().analyticsCollectionEnabled) {
              _analyticsService.setUserId(null);
            }

            const failure = UnauthorizedStoreApiPurchasedItemUpdateHandlingFailure(
              user: updatedUser,
              accessCredentials: updatedAccessCredentials,
            );

            return left(failure);
        }
      }

      purchasedItemRestorationResult = purchasedItemRestorationFailureOrResult.asRight();

      final appStorePurchasedItems = purchasedItemRestorationResult.appStorePurchasedItems;

      apiPurchasedItemEffective = appStorePurchasedItems.any((appStorePurchasedItem) {
        switch (appStorePurchasedItem) {
          case AppStorePurchasedSubscription():
            final appStorePurchasedSubscription = appStorePurchasedItem;

            return appStorePurchasedSubscription.transactions.any((transaction) {
              return transaction.id == transactionId;
            });

          case AppStoreOneTimePurchasedItem():
            final appStoreOneTimePurchasedItem = appStorePurchasedItem;
            return appStoreOneTimePurchasedItem.transaction.id == transactionId;
        }
      });

      if (apiPurchasedItemEffective) {
        break;
      } else {
        await Future.delayed(const Duration(seconds: 1));
      }
    }

    if (apiPurchasedItemEffective) {
      await _googleAppleStoreApiService.finishTransactionIos(
        transactionId: transactionId,
      );
    }

    final premiumAccessInfo = _getPremiumAccessInfoFromPurchasedItems(
      demoExpirationTime: demoExpirationTime,
      appStorePurchasedItems: purchasedItemRestorationResult.appStorePurchasedItems,
      googlePlayPurchasedItems: purchasedItemRestorationResult.googlePlayPurchasedItems,
      webPurchasedItems: purchasedItemRestorationResult.webPurchasedItems,
    );

    final premiumAccessStatus = _createPremiumAccessStatus(
      introFinished: introFinished,
      premiumAccessInfo: premiumAccessInfo,
    );

    unawaited(_userSecureRepository.savePremiumAccessInfo(
      premiumAccessInfo: premiumAccessInfo,
    ));

    final result = StoreApiPurchasedItemUpdateHandlingResult(
      purchasedItemEffective: apiPurchasedItemEffective,
      premiumAccessInfo: premiumAccessInfo,
      premiumAccessStatus: premiumAccessStatus,
    );

    return right(result);
  }

  Future<
      Either<StoreApiPurchasedItemUpdateHandlingFailure,
          StoreApiPurchasedItemUpdateHandlingResult?>> _handleGooglePlayApiPurchasedItemUpdate({
    required GooglePlayApiPurchasedItem googlePlayApiPurchasedItem,
    required String? deviceId,
    required DateTime? demoExpirationTime,
    required bool introFinished,
  }) async {
    final transactionId = googlePlayApiPurchasedItem.transactionId;

    if (googlePlayApiPurchasedItem.purchaseState != GooglePlayApiPurchaseState.purchased) {
      return right(null);
    }

    final failureOrApiPurchasedItems = await _googleAppleStoreApiService.getPurchasedItems();

    if (failureOrApiPurchasedItems.isLeft()) {
      const failure = NetworkStoreApiPurchasedItemUpdateHandlingFailure();
      return left(failure);
    }

    final apiPurchasedItems = failureOrApiPurchasedItems.asRight();

    final newGooglePlayPurchasedItemIdentifier = GooglePlayPurchasedItemIdentifier(
      purchaseToken: googlePlayApiPurchasedItem.purchaseToken,
      purchaseItemSku: googlePlayApiPurchasedItem.purchaseItemSku,
    );

    final previousApiPurchasedItems = apiPurchasedItems.where((apiPreviousPurchasedItem) {
      final apiPreviousGooglePlayPurchasedItem =
          apiPreviousPurchasedItem as GooglePlayApiPurchasedItem;
      return apiPreviousGooglePlayPurchasedItem.transactionId != transactionId;
    });

    final previousGooglePlayPurchasedItemIdentifiers =
        previousApiPurchasedItems.map((apiPurchasedItem) {
      final googlePlayApiPurchasedItem = apiPurchasedItem as GooglePlayApiPurchasedItem;

      return GooglePlayPurchasedItemIdentifier(
        purchaseToken: googlePlayApiPurchasedItem.purchaseToken,
        purchaseItemSku: googlePlayApiPurchasedItem.purchaseItemSku,
      );
    }).toBuiltList();

    final googlePlayNewPurchasedItemSyncFailureOrResult =
        await _purchasedItemApiRepository.syncGooglePlayNewPurchasedItem(
      deviceId: deviceId,
      newPurchasedItemIdentifier: newGooglePlayPurchasedItemIdentifier,
      previousPurchasedItemIdentifiers: previousGooglePlayPurchasedItemIdentifiers,
    );

    if (googlePlayNewPurchasedItemSyncFailureOrResult.isLeft()) {
      final googlePlayNewPurchasedItemSyncFailure =
          googlePlayNewPurchasedItemSyncFailureOrResult.asLeft();

      switch (googlePlayNewPurchasedItemSyncFailure) {
        case ServerGooglePlayNewPurchasedItemSyncFailure():
          const failure = ServerStoreApiPurchasedItemUpdateHandlingFailure();
          return left(failure);

        case NetworkGooglePlayNewPurchasedItemSyncFailure():
          const failure = NetworkStoreApiPurchasedItemUpdateHandlingFailure();
          return left(failure);

        case UnauthorizedGooglePlayNewPurchasedItemSyncFailure():
          const updatedUser = null;
          const updatedAccessCredentials = null;

          _handleAccessCredentialUpdate(
            accessCredentials: updatedAccessCredentials,
          );

          unawaited(_userRepository.saveUser(
            user: updatedUser,
          ));

          if (Config().analyticsCollectionEnabled) {
            _analyticsService.setUserId(null);
          }

          const failure = UnauthorizedStoreApiPurchasedItemUpdateHandlingFailure(
            user: updatedUser,
            accessCredentials: updatedAccessCredentials,
          );

          return left(failure);
      }
    }

    final googlePlayNewPurchasedItemSyncResult =
        googlePlayNewPurchasedItemSyncFailureOrResult.asRight();

    final googlePlayPurchasedItems = googlePlayNewPurchasedItemSyncResult.googlePlayPurchasedItems;

    final apiPurchasedItemEffective = googlePlayPurchasedItems.any((googlePlayPurchasedItem) {
      switch (googlePlayPurchasedItem) {
        case GooglePlayPurchasedSubscription():
          final googlePlayPurchasedSubscription = googlePlayPurchasedItem;

          return googlePlayPurchasedSubscription.transactions.any((transaction) {
            return transaction.orderId == transactionId;
          });

        case GooglePlayOneTimePurchasedItem():
          final googlePlayOneTimePurchasedItem = googlePlayPurchasedItem;
          return googlePlayOneTimePurchasedItem.transaction.orderId == transactionId;
      }
    });

    final premiumAccessInfo = _getPremiumAccessInfoFromPurchasedItems(
      demoExpirationTime: demoExpirationTime,
      appStorePurchasedItems: googlePlayNewPurchasedItemSyncResult.appStorePurchasedItems,
      googlePlayPurchasedItems: googlePlayPurchasedItems,
      webPurchasedItems: googlePlayNewPurchasedItemSyncResult.webPurchasedItems,
    );

    final premiumAccessStatus = _createPremiumAccessStatus(
      introFinished: introFinished,
      premiumAccessInfo: premiumAccessInfo,
    );

    unawaited(_userSecureRepository.savePremiumAccessInfo(
      premiumAccessInfo: premiumAccessInfo,
    ));

    final result = StoreApiPurchasedItemUpdateHandlingResult(
      purchasedItemEffective: apiPurchasedItemEffective,
      premiumAccessInfo: premiumAccessInfo,
      premiumAccessStatus: premiumAccessStatus,
    );

    return right(result);
  }
}
